{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Автор: Алексей Полтавец*<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# <center> Introduction\n",
    "Туториал вдохновлен [kernels](https://www.kaggle.com/jhoward/nb-svm-strong-linear-baseline) и являеться его воплощением на примере [QR кодов](https://www.kaggle.com/c/receipt-categorisation)\n",
    "Далее будем расматривать что такое NBSVM (Naive Bayes - Support Vector Machine) и как его применяют для линейных моделей.\n",
    "\n",
    "NBSVM представлена Sida Wang и Chris Manning в работе: [Baselines and Bigrams: Simple, Good Sentiment and Topic Classiﬁcation](https://nlp.stanford.edu/pubs/sidaw12_simple_sentiment.pdf).\n",
    "\n",
    "Naive Bayes (NB) и Support Vector Machine (SVM) модель, которую используют для улучшения базового линейного метода для категоризации."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### <center>Немного теории из статьи:\n",
    " \n",
    "Базовый линейный метод имеет математическую формулировку:\n",
    "    $y = w_0 + \\sum_{i=1}^m w_i x_i$\n",
    "Задача линейной модели найти коэффициенты $w_i$ при $x_i$.\n",
    "\n",
    "Naive Bayes - Support Vector Machine выполняет вспомагательную функцию (что указано в названии). Суть метода преобразовать коэффициенты $x_i$ так, что бы они были больше для целевой категории и поменьше для других (метод основан на бинарном представлении класса как One vs All). Для этого предлагаеться $x_i$ домножить на коэффициент важности переменной (в тексте для токена). Важность токена выводиться из формулы Байеса:\n",
    "\n",
    "Формула Байеса имеет вид:\n",
    "\n",
    "$$\\large\\begin{array}\n",
    "\\mathcal{P}\\left(A\\mid B\\right) &=& \\frac{P\\left(B\\mid A\\right)P\\left(A\\right)}{P\\left(B\\right)}\n",
    "\\end{array}$$\n",
    "\n",
    "Распишем уравнение для задачи вычисления вероятности отнесения строки к классу $c=1$:\n",
    "\n",
    "$$\\large\\begin{array}\n",
    "\\mathcal{P}\\left(c=1\\mid d\\right) &=& \\frac{P\\left(d\\mid c=1\\right)P\\left(c=1\\right)}{P\\left(d\\right)}\n",
    "\\end{array}$$\n",
    "Разделим все на вероятность отнесения к классу $c=0$:\n",
    "\n",
    "$$\\large\\begin{array}\n",
    "\\mathcal\\frac{P\\left(c=1\\mid d\\right)}{P\\left(c=0\\mid d\\right)} &=& \\frac{P\\left(d\\mid c=1\\right)P\\left(c=1\\right)}{P\\left(d\\right)}\\frac{P\\left(d\\right)}{P\\left(d\\mid c=0\\right)P\\left(c=0\\right)}\n",
    "\\end{array}$$\n",
    "\n",
    "Если полученое больше 1 - скорей всего $c=1$, если меньше - $c=0$. \n",
    "\n",
    "Взяв логорифм выражения получим:\n",
    "\n",
    "$$\\large\\begin{array}\n",
    "\\mathcal{r}&=& \\log\\left(\\frac{p/\\left\\|p\\right\\|_1}{q/\\left\\| q\\right\\|_1}\\right)\n",
    "\\end{array}$$\n",
    "\n",
    "\n",
    "Где: $\\begin{array}\\mathcal{p}&=& \\alpha + \\sum_{i : y^\\left(i\\right) = 1} f^\\left(i\\right)\\end{array}$ и\n",
    "$\\begin{array}\\mathcal{q}&=& \\alpha + \\sum_{i : y^\\left(i\\right) = 0} f^\\left(i\\right)\\end{array}$, $\\alpha$ - коэффициент сглаживания (выбираеться исходя из задачи). \n",
    "\n",
    "$p$ то как часто данный $x_i$ встречаеться при $y = 1$, $q$ - при $y = 0$."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Перейдем к воплощению метода в коде:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np  # linear algebra\n",
    "import pandas as pd  # data processing, CSV file I/O (e.g. pd.read_csv)\n",
    "from sklearn.feature_extraction.text import CountVectorizer\n",
    "from sklearn.linear_model import LogisticRegression\n",
    "from sklearn.metrics import log_loss\n",
    "from sklearn.model_selection import GroupKFold, cross_val_score\n",
    "from sklearn.naive_bayes import MultinomialNB\n",
    "from sklearn.preprocessing import LabelBinarizer, LabelEncoder"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Read data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train = pd.read_csv(\"train.csv.gz\")\n",
    "train.fillna(\"\", inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vectorizer = CountVectorizer()\n",
    "labeler = LabelEncoder()\n",
    "lb = LabelBinarizer()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train.head(3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X = vectorizer.fit_transform(train.name.values)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "y = labeler.fit_transform(train.category)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "Разбиваем обучающую выборку на части. Для демонстрации NB-SVM будем брать только первый fold.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gkf = list(GroupKFold(n_splits=3).split(X, y, train.check_id.values))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Создадим матрицу в бинарном представлении категорий:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "labels = lb.fit_transform(y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "logist = LogisticRegression()\n",
    "mnb = MultinomialNB()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Быстрая проверка какие результаты дают LogisticRegression и MultinomialNB."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "score = cross_val_score(logist, X, y, scoring=\"neg_log_loss\", cv=gkf, n_jobs=-1)\n",
    "print(\"%.3f +- %.4f\" % (-np.mean(score), np.std(score)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Результаты для первого фолда методом LogisticRegression = %.3f\" % (-score[0]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "score = cross_val_score(mnb, X, y, scoring=\"neg_log_loss\", cv=gkf, n_jobs=-1)\n",
    "print(\"%.3f +- %.4f\" % (-np.mean(score), np.std(score)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Результаты для первого фолда методом MultinomialNB = %.3f\" % (-score[0]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как видим оба метода примерно одинаковы по результатам (MultinomialNB слегка лучше)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Разделим train на X_train и X_valid, взяв за разбиение первый фолд созданый GroupKFold"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "#### Use 1st slit for example NB - SVM"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train, X_valid, y_train, y_valid = (\n",
    "    X[gkf[0][0]],\n",
    "    X[gkf[0][1]],\n",
    "    labels[gkf[0][0], :],\n",
    "    labels[gkf[0][1], :],\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Разбивка на 3 части : у нас 0,66 train part и 0,33 valid part\n",
    "X_train.shape, X_valid.shape, y_train.shape, y_valid.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Кратко о MultinomialNB. Модель создает матрицу в которой указываеться, как часто каждая из характеристик (у нас тут tokens из слов) встречаеться при конкретном классе. Параметр сглаживания $\\alpha$ являеться гиперпараметром для модели. В формуле ratio у нас под логорифмом дробь, по правилам логарифмирования это разница логарифмов.\n",
    "$$\\begin{array}\n",
    "\\mathcal{r}&=& \\log\\left(\\frac{p/\\left\\|p\\right\\|_1}{q/\\left\\| q\\right\\|_1}\\right)\n",
    "&=& \\log\\left(p/\\left\\|p\\right\\|_1\\right) - \\log\\left(q/\\left\\|q\\right\\|_1\\right)\n",
    "\\end{array}$$\n",
    "У MultinomialNB есть метод который вернет нам эти слогаемые: feature_log_prob_$[1]$ и feature_log_prob_$[0]$. Остаеться применить полученые коэффициенты к data.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "preds = []\n",
    "for idx, category in enumerate(labeler.classes_):\n",
    "    # print (' Prediction for %s' % category)\n",
    "    mnb.fit(X_train, y_train[:, idx])\n",
    "    ratio = mnb.feature_log_prob_[1] - mnb.feature_log_prob_[0]\n",
    "    logist.fit(X_train.multiply(ratio), y_train[:, idx])\n",
    "    preds.append(logist.predict_proba(X_valid.multiply(ratio))[:, 1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\n",
    "    \"Score after applying NB-SVM on data = %.3f\" % log_loss(y_valid, np.array(preds).T)\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Результат NB-SVM лечше отдельно каждого их методов 0,799 и 0,744. После применения имеем 0,650."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
